<!DOCTYPE html><html lang="zh"><head><meta name="generator" content="Hexo 3.8.0"><meta charset="utf-8"><title>Spring Data JPA源码分析-方法命名查询 - i蝸居年華_谢谢谢_CODE_HOME</title><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1"><meta name="description" content="[TOC] 前言为什么要学习Spring Data JPA, 这里引用《Spring Data JPA实战》一书的一段话"><meta name="keywords" content="Spring Data,源码分析,Spring全家桶"><meta property="og:type" content="article"><meta property="og:title" content="Spring Data JPA源码分析-方法命名查询"><meta property="og:url" content="https://xiefayang.com/2018/06/01/Spring Data JPA 源码分析 - 方法命名查询/index.html"><meta property="og:site_name" content="i蝸居年華_谢谢谢_CODE_HOME"><meta property="og:description" content="[TOC] 前言为什么要学习Spring Data JPA, 这里引用《Spring Data JPA实战》一书的一段话"><meta property="og:locale" content="zh-CN"><meta property="og:image" content="https://xiefayang.com/thumbnails/SpringData.png"><meta property="og:updated_time" content="2019-05-21T03:18:47.903Z"><meta name="twitter:card" content="summary"><meta name="twitter:title" content="Spring Data JPA源码分析-方法命名查询"><meta name="twitter:description" content="[TOC] 前言为什么要学习Spring Data JPA, 这里引用《Spring Data JPA实战》一书的一段话"><meta name="twitter:image" content="https://xiefayang.com/thumbnails/SpringData.png"><link rel="icon" href="/images/favicon.svg"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.7.2/css/bulma.css"><link rel="stylesheet" href="https://cdn.bootcss.com/font-awesome/5.4.1/css/all.css"><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Ubuntu:400,600|Source+Code+Pro"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@9.12.0/styles/atom-one-dark.css"><style>body>.footer,body>.navbar,body>.section{opacity:0}</style><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/lightgallery@1.6.8/dist/css/lightgallery.min.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/justifiedGallery@3.7.0/dist/css/justifiedGallery.min.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/outdatedbrowser@1.1.5/outdatedbrowser/outdatedbrowser.min.css"><link rel="stylesheet" href="/css/back-to-top.css"><script>var _hmt=_hmt||[];!function(){var e=document.createElement("script");e.src="//hm.baidu.com/hm.js?b59777544dd5d59ce94e191bac62427c";var t=document.getElementsByTagName("script")[0];t.parentNode.insertBefore(e,t)}()</script><link rel="stylesheet" href="/css/progressbar.css"><script src="https://cdn.jsdelivr.net/npm/pace-js@1.0.2/pace.min.js"></script><script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script><link rel="stylesheet" href="/css/style.css"></head><body class="is-3-column"><nav class="navbar navbar-main"><div class="container"><div class="navbar-brand is-flex-center"> <a class="navbar-item navbar-logo" href="/"><img src="/images/maotouying.svg" alt="Spring Data JPA源码分析-方法命名查询" height="28"> <span style="font-size:16px;display:inline-block;vertical-align:top;font-weight:600">i蝸居年華_谢谢谢</span></a></div><div class="navbar-menu"><div class="navbar-start"> <a class="navbar-item" href="/">首页</a> <a class="navbar-item" href="/archives">归档</a> <a class="navbar-item" href="/categories">分类</a> <a class="navbar-item" href="/tags">标签</a> <a class="navbar-item" href="/about">关于</a></div><div class="navbar-end"><a class="navbar-item is-hidden-tablet catalogue" title="文章目录" href="javascript:;"><i class="fas fa-list-ul"></i></a><a class="navbar-item search" title="搜索" href="javascript:;"><div class="box01"> <input type="text" id="tip" placeholder="You Know, for Search~"><i class="search_icon"></i></div></a></div></div></div></nav><section class="section"><div class="container"><div class="columns"><div class="column is-8-tablet is-8-desktop is-9-widescreen has-order-2 column-main"><div class="card"><div class="card-content article"><div class="level article-meta is-size-7 is-uppercase is-mobile is-overflow-x-auto"><div class="level-left"> <span style="color:#7a7a7a!important">写于2018-06-01 12:39:04&nbsp;&nbsp;&nbsp;</span><div class="level-item"> <a class="has-link-grey -link" href="/categories/Spring-全家桶/">Spring 全家桶</a>&nbsp;/&nbsp;<a class="has-link-grey -link" href="/categories/Spring-全家桶/Spring-Data-系列/">Spring Data 系列</a></div> <span class="level-item has-text-grey">25 分钟 读完 (大约 3741 个字)</span><span class="level-item has-text-grey" id="busuanzi_container_page_pv"><i class="far fa-eye"></i> <span id="busuanzi_value_page_pv">0</span>次访问</span></div></div><h1 class="title is-size-3 is-size-4-mobile has-text-weight-normal"> Spring Data JPA源码分析-方法命名查询</h1><div class="content"><p>[TOC]</p><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>为什么要学习Spring Data JPA, 这里引用《Spring Data JPA实战》一书的一段话</p><a id="more"></a><blockquote><p>为什么要重新学习“Spring Data JPA”？俗话说的好：“未来已经来临，只是尚未流行”，纵观市场上的 ORM 框架，MyBatis 以灵活著称，但是要维护复杂的配置，并且不是 Spring 官方的天然全家桶，还得做额外的配置工作，如果资深的架构师还得做很多封装；Hibernate 以 HQL 和关系映射著称，但是就是使用起来不是特别灵活；那么 Spring Data JPA 来了，感觉要夺取 ORM 的 JPA 霸主地位了，底层以 Hibernate 为封装，对外提供了超级灵活的使用接口，又非常符合面向对象和 Rest 的风格，感觉是架构师和开发者的福音，并且 Spring Data JPA 与 Spring Boot 配合起来使用具有天然的优势，你会发现越来越多的公司的招聘要用会有传统的 SSH、Spring、MyBatis 要求，逐步的变为 Spring Boot、Spring Cloud、Spring Data 等 Spring 全家桶的要求，而很多新生代的架构师基于其生态的考虑，正在逐步推动者 Spring Data JPA 的更多的使用场景。</p></blockquote><h3 id="方法命名查询"><a href="#方法命名查询" class="headerlink" title="方法命名查询"></a>方法命名查询</h3><p>方法命名查询: Defining Query Method</p><blockquote><p>在最初, 我用到方法命名查询时觉得很酷, 这应该算是Spring Data JPA的一大特色了.</p></blockquote><p>在Spring Data JPA中可以用创建规范的自定义方法进行查询</p><p>尽管这种方式是很方便的, 但是难免会遇到这样的情况:</p><ul><li>方法名解析器不支持要使用的关键字</li><li>复杂糟糕的查询约束会导致方法名也变得很糟糕(例如: <code>findByXxxXxxXxInXxxOrderXxxXxx...</code>)</li></ul><p>当然Specification, @Query能够解决这样的情况, 但并不是本节的重点.</p><p>首先要知道, 在自定义方法名称中, 只需要在自己的实体仓储(Repository)上继承<code>Repository</code>接口即可.</p><p>当然, 如果想要有其它默认的通用方法实现, 可以有选择性的继承CrudRepository, PagingAndSortingRepository, JpaRepository等接口.</p><p>以及JpaSpecificationExecutor, QueryByExampleExecutor, QuerydslPredicateExecutor 和自定义Repository, 都可以达到同样的效果.</p><p>并且在默认情况下, 通用的默认方法(例如: <code>findOne(), save()...</code>)无需我们实现, <code>SimpleJpaRepository</code>会对其提供实现.</p><h4 id="Repository作用"><a href="#Repository作用" class="headerlink" title="Repository作用"></a>Repository作用</h4><p>为什么继承一个<code>Repository</code>接口就可以呢?</p><p>首先<code>Repository</code>由Spring Data Common提供, 如果点进去可以发现它是一个空接口, 没有任何方法声明, 也就是常说的标记接口(Mark Interface).</p><p>JDK中提供了也存在这样的接口: 例如最常见的<code>Serializable</code></p><p>而标记接口的作用就是当某个类去实现了标记接口, 就会认为这个类具有了标记的某种能力.</p><p>观察<code>Repository</code>的层次关系, 大致可以分出三条路线来:</p><ul><li><code>CrudRepository -&gt; PagingAndSortingRepository -&gt; JpaRepository -&gt; SimpleJpaRepository</code></li><li><code>ReactiveRepository</code></li><li><code>RxJava2CrudRepository</code></li></ul><p>需要知道的是: <code>Repository</code>, <code>CrudRepository</code>, <code>PagingAndSortingRepository</code>都是Spring Data Common下的标准接口, 而到了<code>JpaRepository</code> 开始才进入了Spring Data JPA模块</p><h4 id="Spring-Data-公共模块"><a href="#Spring-Data-公共模块" class="headerlink" title="Spring Data 公共模块"></a>Spring Data 公共模块</h4><p>所以到这里也能大致看出Spring Data下模块划分的意义</p><p>Spring Data Common作为Spring Data所有模块的公用部分.</p><p>如果我们使用JPA, 那它的实现就是Spring Data JPA下的SimpleJpaRepository.</p><p>如果是其他NoSQL实现, 例如redis, 那他的实现就是Spring Data Redis里的SimpleKeyValueRepository</p><p>提供Repository默认方法实现的源码并不会在这里分析, 因为它不是本节的重点.</p><blockquote><p>在接口中定义规范的方法名就可以推导出查询来, 我们并不需要做任何实现, 那么Spring Data JPA是如何做到的呢?</p></blockquote><h3 id="源码分析"><a href="#源码分析" class="headerlink" title="源码分析"></a>源码分析</h3><h4 id="Repository工厂类"><a href="#Repository工厂类" class="headerlink" title="Repository工厂类"></a>Repository工厂类</h4><p>我们在Java Config方式配置Spring Data JPA的@EnableJpaRepositories注解里可以发现这样一句:</p><p><code>Class&lt;?&gt; repositoryFactoryBeanClass() default JpaRepositoryFactoryBean.class;</code></p><p>配置中默认指定了Repository的工厂类 -&gt; JpaRepositoryFactoryBean</p><p>来看下源码:</p><figure class="highlight java hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">JpaRepositoryFactoryBean</span>&lt;<span class="hljs-title">T</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Repository</span>&lt;<span class="hljs-title">S</span>, <span class="hljs-title">ID</span>&gt;, <span class="hljs-title">S</span>, <span class="hljs-title">ID</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Serializable</span>&gt; <span class="hljs-keyword">extends</span> <span class="hljs-title">TransactionalRepositoryFactoryBeanSupport</span>&lt;<span class="hljs-title">T</span>, <span class="hljs-title">S</span>, <span class="hljs-title">ID</span>&gt; </span>&#123;</span><br><span class="line">	<span class="hljs-comment">//...</span></span><br><span class="line">    <span class="hljs-keyword">private</span> EntityManager entityManager;</span><br><span class="line">    </span><br><span class="line">    <span class="hljs-meta">@Override</span></span><br><span class="line">	<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">afterPropertiesSet</span><span class="hljs-params">()</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">		Assert.notNull(entityManager, <span class="hljs-string">"EntityManager must not be null!"</span>);</span><br><span class="line">		<span class="hljs-keyword">super</span>.afterPropertiesSet();</span><br><span class="line">	&#125;</span><br><span class="line">    <span class="hljs-comment">//...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>通过这个类的命名可以猜到是个工厂Bean, 并且有看到Bean初始化后的回调处理<code>afterPropertiesSet()</code></p><p>所以立刻点进它的父类继承到抽象类<code>RepositoryFactoryBeanSupport</code>中</p><p>没错! 它实现InitializingBean接口和FactoryBean接口.</p><p>接下来就是寻找这个RepositoryFactoryBean是在哪里得到的呢.</p><p>自然是从获取工厂实例的实现方法下手:</p><p><code>RepositoryFactoryBeanSupport.getObject()</code>:</p><figure class="highlight java hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-function"><span class="hljs-keyword">public</span> T <span class="hljs-title">getObject</span><span class="hljs-params">()</span> </span>&#123;</span><br><span class="line">    <span class="hljs-keyword">return</span> initAndReturn();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="hljs-keyword">public</span> Class&lt;? extends T&gt; getObjectType() &#123;</span><br><span class="line">		<span class="hljs-keyword">return</span> (Class&lt;? extends T&gt;) (<span class="hljs-keyword">null</span> == repositoryInterface ? Repository.class : repositoryInterface);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="hljs-comment">/*</span></span><br><span class="line"><span class="hljs-comment"> * 这里是单例</span></span><br><span class="line"><span class="hljs-comment"> * @see org.springframework.beans.factory.FactoryBean#isSingleton()</span></span><br><span class="line"><span class="hljs-comment"> */</span></span><br><span class="line"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">isSingleton</span><span class="hljs-params">()</span> </span>&#123;</span><br><span class="line">		<span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>RepositoryFactoryBeanSupport.initAndReturn()</code>:</p><figure class="highlight java hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-comment">/** </span></span><br><span class="line"><span class="hljs-comment">  * Returns the previously initialized repository proxy or creates </span></span><br><span class="line"><span class="hljs-comment">  * and returns the proxy if previously uninitialized.</span></span><br><span class="line"><span class="hljs-comment">  */</span></span><br><span class="line"><span class="hljs-function"><span class="hljs-keyword">private</span> T <span class="hljs-title">initAndReturn</span><span class="hljs-params">()</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    Assert.notNull(repositoryInterface, <span class="hljs-string">"Repository interface must not be null on initialization!"</span>);</span><br><span class="line"></span><br><span class="line">    <span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>.repository == <span class="hljs-keyword">null</span>) &#123;</span><br><span class="line">        <span class="hljs-comment">// 看这里</span></span><br><span class="line">        <span class="hljs-keyword">this</span>.repository = <span class="hljs-keyword">this</span>.factory.getRepository(repositoryInterface, customImplementation);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.repository;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到Repository Instance是在<code>this.factory</code>中得到, 那么再追入<code>RepositoryFactorySupport factory</code>.</p><h4 id="RepositoryFactorySupport-factory的创建"><a href="#RepositoryFactorySupport-factory的创建" class="headerlink" title="RepositoryFactorySupport#factory的创建"></a>RepositoryFactorySupport#factory的创建</h4><p>不难找到, 其实上面已经提到过bean初始化回调方法<code>afterPropertiesSet()</code> , 就是在这里创建的<code>factory</code></p><figure class="highlight java hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">afterPropertiesSet</span><span class="hljs-params">()</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">		Assert.notNull(repositoryInterface, <span class="hljs-string">"Repository interface must not be null on initialization!"</span>);</span><br><span class="line"></span><br><span class="line">    	<span class="hljs-comment">// 看这</span></span><br><span class="line">		<span class="hljs-keyword">this</span>.factory = createRepositoryFactory();</span><br><span class="line">		<span class="hljs-keyword">this</span>.factory.setQueryLookupStrategyKey(queryLookupStrategyKey);</span><br><span class="line">		<span class="hljs-keyword">this</span>.factory.setNamedQueries(namedQueries);</span><br><span class="line">		<span class="hljs-keyword">this</span>.factory.setEvaluationContextProvider(evaluationContextProvider);</span><br><span class="line">		<span class="hljs-keyword">this</span>.factory.setRepositoryBaseClass(repositoryBaseClass);</span><br><span class="line">		<span class="hljs-keyword">this</span>.factory.setBeanClassLoader(classLoader);</span><br><span class="line">		<span class="hljs-keyword">this</span>.factory.setBeanFactory(beanFactory);</span><br><span class="line"></span><br><span class="line">		<span class="hljs-keyword">this</span>.repositoryMetadata = <span class="hljs-keyword">this</span>.factory.getRepositoryMetadata(repositoryInterface);</span><br><span class="line"></span><br><span class="line">		<span class="hljs-keyword">if</span> (!lazyInit) &#123;</span><br><span class="line">			initAndReturn();</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br></pre></td></tr></table></figure><p><code>createRepositoryFactory()</code>的实现是在TransactionalRepositoryFactoryBeanSupport中</p><figure class="highlight java hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-meta">@Override</span></span><br><span class="line"><span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">final</span> RepositoryFactorySupport <span class="hljs-title">createRepositoryFactory</span><span class="hljs-params">()</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">	RepositoryFactorySupport factory = doCreateRepositoryFactory();</span><br><span class="line">	factory.addRepositoryProxyPostProcessor(exceptionPostProcessor);</span><br><span class="line">	factory.addRepositoryProxyPostProcessor(txPostProcessor);</span><br><span class="line"></span><br><span class="line">	<span class="hljs-keyword">return</span> factory;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>再往下查看源码实际就是在JpaRepositoryFactoryBean中注入<code>entityManager</code>后 <code>return new JpaRepositoryFactory(entityManager);</code> 然后添加两个PostProcessor后返回factory</p><p>继续回到<code>RepositoryFactoryBeanSupport.initAndReturn()</code></p><figure class="highlight java hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-keyword">this</span>.repository = <span class="hljs-keyword">this</span>.factory.getRepository(repositoryInterface, customImplementation);</span><br></pre></td></tr></table></figure><p>####获取Repository实例代理</p><p><code>getRepository()</code> 的实现就在JpaRepositoryFactory的父类RepositoryFactorySupport中:</p><figure class="highlight java hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-keyword">public</span> &lt;T&gt; <span class="hljs-function">T <span class="hljs-title">getRepository</span><span class="hljs-params">(Class&lt;T&gt; repositoryInterface, Object customImplementation)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">		RepositoryMetadata metadata = getRepositoryMetadata(repositoryInterface);</span><br><span class="line">		Class&lt;?&gt; customImplementationClass = <span class="hljs-keyword">null</span> == customImplementation ? <span class="hljs-keyword">null</span> : customImplementation.getClass();</span><br><span class="line">		RepositoryInformation information = getRepositoryInformation(metadata, customImplementationClass);</span><br><span class="line"></span><br><span class="line">		validate(information, customImplementation);</span><br><span class="line"></span><br><span class="line">		Object target = getTargetRepository(information);</span><br><span class="line"></span><br><span class="line">		<span class="hljs-comment">// Create proxy</span></span><br><span class="line">		ProxyFactory result = <span class="hljs-keyword">new</span> ProxyFactory();</span><br><span class="line">		result.setTarget(target);</span><br><span class="line">		result.setInterfaces(<span class="hljs-keyword">new</span> Class[] &#123; repositoryInterface, Repository.class &#125;);</span><br><span class="line"></span><br><span class="line">		result.addAdvice(ExposeInvocationInterceptor.INSTANCE);</span><br><span class="line"></span><br><span class="line">		<span class="hljs-keyword">if</span> (TRANSACTION_PROXY_TYPE != <span class="hljs-keyword">null</span>) &#123;</span><br><span class="line">			result.addInterface(TRANSACTION_PROXY_TYPE);</span><br><span class="line">		&#125;</span><br><span class="line"></span><br><span class="line">		<span class="hljs-keyword">for</span> (RepositoryProxyPostProcessor processor : postProcessors) &#123;</span><br><span class="line">			processor.postProcess(result, information);</span><br><span class="line">		&#125;</span><br><span class="line"></span><br><span class="line">		<span class="hljs-keyword">if</span> (IS_JAVA_8) &#123;</span><br><span class="line">			result.addAdvice(<span class="hljs-keyword">new</span> DefaultMethodInvokingMethodInterceptor());</span><br><span class="line">		&#125;</span><br><span class="line"></span><br><span class="line">		result.addAdvice(<span class="hljs-keyword">new</span> QueryExecutorMethodInterceptor(information, customImplementation, target));</span><br><span class="line"></span><br><span class="line">		<span class="hljs-keyword">return</span> (T) result.getProxy(classLoader);</span><br><span class="line">	&#125;</span><br></pre></td></tr></table></figure><p>在这个实现类中, 有获取仓库元数据, 验证, 注册方法拦截器的advice等.</p><blockquote><p>注意: 在创建ProxyFactory实例后, 调用<code>getProxy()</code>返回的是Repository接口代理.</p><p>这也是常说的: Spring Data JPA 的实现原理是动态代理机制</p></blockquote><p>到此, 已经大致了解了一些工厂Bean<code>JpaRepositoryFactoryBean</code>所做的一些事情.</p><p>接下来分析方法命名查询的重点就在 <code>QueryExecutorMethodIntrceptor</code>中了.</p><p>####QueryExecutorMethodIntrceptor方法拦截</p><p>首先看到它是RepositoryFactorySupport 的内部类, 并实现MethodInterceptor方法拦截器接口</p><p>我们在Repository接口中定义的查询方法是怎么被识别的? 在方法调用之前又经过了哪些处理?</p><p>那么在这个拦截器中应该能找到想要的答案</p><p>在实例化时执行构造方法: <code>QueryExecutorMethodInterceptor()</code></p><figure class="highlight java hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">QueryExecutorMethodInterceptor</span><span class="hljs-params">(RepositoryInformation repositoryInformation, Object customImplementation, Object target)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">	<span class="hljs-comment">//...</span></span><br><span class="line">	QueryLookupStrategy lookupStrategy = getQueryLookupStrategy(queryLookupStrategyKey,</span><br><span class="line">					RepositoryFactorySupport.<span class="hljs-keyword">this</span>.evaluationContextProvider);</span><br><span class="line">	lookupStrategy = lookupStrategy == <span class="hljs-keyword">null</span> ? getQueryLookupStrategy(queryLookupStrategyKey) : lookupStrategy;</span><br><span class="line">	Iterable&lt;Method&gt; queryMethods = repositoryInformation.getQueryMethods();</span><br><span class="line"></span><br><span class="line">	<span class="hljs-keyword">if</span> (lookupStrategy == <span class="hljs-keyword">null</span>) &#123;</span><br><span class="line"></span><br><span class="line">		<span class="hljs-keyword">if</span> (queryMethods.iterator().hasNext()) &#123;</span><br><span class="line">			<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> IllegalStateException(<span class="hljs-string">"You have defined query method in the repository but "</span></span><br><span class="line">							+ <span class="hljs-string">"you don't have any query lookup strategy defined. The "</span></span><br><span class="line">							+ <span class="hljs-string">"infrastructure apparently does not support query methods!"</span>);</span><br><span class="line">		&#125;</span><br><span class="line"></span><br><span class="line">		<span class="hljs-keyword">return</span>;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">    SpelAwareProxyProjectionFactory factory = <span class="hljs-keyword">new</span> SpelAwareProxyProjectionFactory();</span><br><span class="line">    factory.setBeanClassLoader(classLoader);</span><br><span class="line">    factory.setBeanFactory(beanFactory);</span><br><span class="line"></span><br><span class="line">    <span class="hljs-keyword">for</span> (Method method : queryMethods) &#123;</span><br><span class="line"></span><br><span class="line">        RepositoryQuery query = lookupStrategy.resolveQuery(method, repositoryInformation, factory, namedQueries);</span><br><span class="line"></span><br><span class="line">        invokeListeners(query);</span><br><span class="line">        queries.put(method, query);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>该方法的有这样一句原文注释<code>Builds a model of {@link QueryMethod}s to be invoked on execution of repository interface methods.</code></p><p>上面的源码大致可以分为两部分:</p><ul><li>获取lookupStrategy(查找策略)</li><li>对Method进行迭代遍历, 根据lookupStrategy(查找策略)查询RepositoryQuery</li><li>为其添加监听</li><li>将Method和RepositoryQuery放入<code>Map&lt;Method, RepositoryQuery&gt; queries</code>缓存中</li></ul><p>一句话总结就是为Repository接口中每个查询定义方法Method去构造相应的RepositoryQuery, RepositoryQuery的相关概念后面还会分析到.</p><h4 id="lookupStrategy方法查询定义的查找策略"><a href="#lookupStrategy方法查询定义的查找策略" class="headerlink" title="lookupStrategy方法查询定义的查找策略"></a>lookupStrategy方法查询定义的查找策略</h4><p>先从获取lookupStrategy(查找策略)看起:</p><p><code>org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy#create</code></p><figure class="highlight java hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> QueryLookupStrategy <span class="hljs-title">create</span><span class="hljs-params">(EntityManager em, Key key, QueryExtractor extractor,</span></span></span><br><span class="line"><span class="hljs-function"><span class="hljs-params">			EvaluationContextProvider evaluationContextProvider)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">		Assert.notNull(em, <span class="hljs-string">"EntityManager must not be null!"</span>);</span><br><span class="line">		Assert.notNull(extractor, <span class="hljs-string">"QueryExtractor must not be null!"</span>);</span><br><span class="line">		Assert.notNull(evaluationContextProvider, <span class="hljs-string">"EvaluationContextProvider must not be null!"</span>);</span><br><span class="line"></span><br><span class="line">		<span class="hljs-keyword">switch</span> (key != <span class="hljs-keyword">null</span> ? key : Key.CREATE_IF_NOT_FOUND) &#123;</span><br><span class="line">			<span class="hljs-keyword">case</span> CREATE:</span><br><span class="line">				<span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> CreateQueryLookupStrategy(em, extractor);</span><br><span class="line">			<span class="hljs-keyword">case</span> USE_DECLARED_QUERY:</span><br><span class="line">				<span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> DeclaredQueryLookupStrategy(em, extractor, evaluationContextProvider);</span><br><span class="line">			<span class="hljs-keyword">case</span> CREATE_IF_NOT_FOUND:</span><br><span class="line">				<span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> CreateIfNotFoundQueryLookupStrategy(em, extractor, <span class="hljs-keyword">new</span> CreateQueryLookupStrategy(em, extractor),</span><br><span class="line">						<span class="hljs-keyword">new</span> DeclaredQueryLookupStrategy(em, extractor, evaluationContextProvider));</span><br><span class="line">			<span class="hljs-keyword">default</span>:</span><br><span class="line">				<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> IllegalArgumentException(String.format(<span class="hljs-string">"Unsupported query lookup strategy %s!"</span>, key));</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br></pre></td></tr></table></figure><p>通过这段switch代码的条件判断和这三种策略对应的不同实现类, 可以总结出以下内容:</p><p>方法的查询策略有三种:</p><ol><li><code>Create</code>: 通过解析规范命名的方法名来创建查询, 此时即使有<code>@Query</code>或<code>@NameQuery</code>也会忽略. 如果方法名不符合规则，启动的时候会报异常</li><li><code>USE_DECLARED_QUERY</code>: 也就是使用注解方式声明式创建, 启动时就会去尝试找声明的查询, 也就是使用<code>@Query</code>定义的语句来执行查询, 没有则找<code>@NameQuery</code>的, 再没有就异常.</li><li><code>CREATE_IF_NOT_FOUND</code>: 先找<code>@Query</code>或<code>@NameQuery</code>定义的语句来查询, 如果没有就通过解析方法名来创建查询.</li></ol><p>其中, 第三种方式<code>CREATE_IF_NOT_FOUND</code>相当于第一种和第二种的结合版. 也是默认的配置策略.</p><blockquote><p>QueryLookupStrategy 是策略的定义接口，JpaQueryLookupStrategy 是具体策略的实现类</p></blockquote><p>本节的重点是自定义方法命名的推导策略, 所以2和3就先略过, 直接找<code>Create</code>策略对应的实现类<code>CreateQueryLookupStrategy</code></p><figure class="highlight java hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CreateQueryLookupStrategy</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">AbstractQueryLookupStrategy</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">	<span class="hljs-comment">//...</span></span><br><span class="line">	<span class="hljs-meta">@Override</span></span><br><span class="line">	<span class="hljs-function"><span class="hljs-keyword">protected</span> RepositoryQuery <span class="hljs-title">resolveQuery</span><span class="hljs-params">(JpaQueryMethod method, EntityManager em, NamedQueries namedQueries)</span> </span>&#123;</span><br><span class="line">		<span class="hljs-comment">//...</span></span><br><span class="line">		<span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> PartTreeJpaQuery(method, em, persistenceProvider);</span><br><span class="line">    	<span class="hljs-comment">//...    </span></span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>####PartTreeJpaQuery实例推导</p><p>这时, 终于来到了实现name和method的拆分逻辑逻辑的方法了. 重点<code>PartTreeJpaQuery</code></p><figure class="highlight java hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-comment">/**</span></span><br><span class="line"><span class="hljs-comment"> * A &#123;<span class="hljs-doctag">@link</span> AbstractJpaQuery&#125; implementation based on a &#123;<span class="hljs-doctag">@link</span> PartTree&#125;.</span></span><br><span class="line"><span class="hljs-comment"> */</span></span><br><span class="line"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PartTreeJpaQuery</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">AbstractJpaQuery</span> </span>&#123;</span><br><span class="line">    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Class&lt;?&gt; domainClass;</span><br><span class="line">	<span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> PartTree tree;</span><br><span class="line">	<span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> JpaParameters parameters;</span><br><span class="line"></span><br><span class="line">	<span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> QueryPreparer query;</span><br><span class="line">	<span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> QueryPreparer countQuery;</span><br><span class="line">	<span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> EntityManager em;</span><br><span class="line"></span><br><span class="line">	<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">PartTreeJpaQuery</span><span class="hljs-params">(JpaQueryMethod method, EntityManager em, PersistenceProvider persistenceProvider)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">		<span class="hljs-keyword">super</span>(method, em);</span><br><span class="line"></span><br><span class="line">		<span class="hljs-keyword">this</span>.em = em;</span><br><span class="line">		<span class="hljs-keyword">this</span>.domainClass = method.getEntityInformation().getJavaType();</span><br><span class="line">		<span class="hljs-keyword">this</span>.parameters = method.getParameters();</span><br><span class="line">		<span class="hljs-comment">// 。。。</span></span><br><span class="line"></span><br><span class="line">		<span class="hljs-keyword">try</span> &#123;</span><br><span class="line"></span><br><span class="line">			<span class="hljs-keyword">this</span>.tree = <span class="hljs-keyword">new</span> PartTree(method.getName(), domainClass);</span><br><span class="line">			<span class="hljs-keyword">this</span>.countQuery = <span class="hljs-keyword">new</span> CountQueryPreparer(persistenceProvider, recreationRequired);</span><br><span class="line">			<span class="hljs-keyword">this</span>.query = tree.isCountProjection() ? countQuery : <span class="hljs-keyword">new</span> QueryPreparer(persistenceProvider, recreationRequired);</span><br><span class="line">			<span class="hljs-comment">//。。。</span></span><br><span class="line">		&#125; </span><br><span class="line">	&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="hljs-comment">// 。。。</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>看到注释, 是基于<code>PartTree实现</code> 呢么一会的重点就是它了</p><p>其次, 它继承了抽象类<code>AbstractJpaQuery</code> 看下它的层次结构:</p><figure class="highlight plain hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">AbstractJpaQuery</span><br><span class="line">	- StoredProcedureJpaQuery</span><br><span class="line">	- PartTreeJpaQuery</span><br><span class="line">	- NamedQuery</span><br><span class="line">	- AbstractStringBasedJpaQuery</span><br><span class="line">		- SimpleJpaQuery</span><br><span class="line">		- NativeJpaQuery</span><br></pre></td></tr></table></figure><ul><li><p><strong>SimpleJpaQuery</strong>, <strong>NativeJpaQuery</strong>: 就是@Query查询定义中的<code>nativeQuery=false|true</code></p><p>前者使用JPQL, 后者使用原生SQL</p></li><li><p><strong>NamedQuery</strong>: 不用解释了, @NamedQuery</p></li><li><strong>StoredProcedureJpaQuery</strong>: 根据名字也猜的差不多, 是跟存储过程调用有关的查询</li><li><strong>PartTreeJpaQuery</strong>: 这就是本节的重点, 方法命名查询的Query实例</li></ul><p>到这里可以总结下:</p><p>RepositoryQuery代表Repository接口中的一个查询方法</p><p>RepositoryQuery中持有QueryMethod实例, QueryMethod中持有Method实例.</p><p>RepositoryQuery根据Repository接口中方法查询定义的不同, 被实例化成不同的子类实现.</p><h4 id="PartTree语法树"><a href="#PartTree语法树" class="headerlink" title="PartTree语法树"></a>PartTree语法树</h4><p>回到<code>PartTree</code>中</p><figure class="highlight java hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PartTree</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Iterable</span>&lt;<span class="hljs-title">OrPart</span>&gt; </span>&#123;</span><br><span class="line">	<span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String KEYWORD_TEMPLATE = <span class="hljs-string">"(%s)(?=(\\p&#123;Lu&#125;|\\P&#123;InBASIC_LATIN&#125;))"</span>;</span><br><span class="line">	<span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String QUERY_PATTERN = <span class="hljs-string">"find|read|get|query|stream"</span>;</span><br><span class="line">	<span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String COUNT_PATTERN = <span class="hljs-string">"count"</span>;</span><br><span class="line">	<span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String EXISTS_PATTERN = <span class="hljs-string">"exists"</span>;</span><br><span class="line">	<span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String DELETE_PATTERN = <span class="hljs-string">"delete|remove"</span>;</span><br><span class="line">	<span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> Pattern PREFIX_TEMPLATE = Pattern.compile( <span class="hljs-comment">//</span></span><br><span class="line">			<span class="hljs-string">"^("</span> + QUERY_PATTERN + <span class="hljs-string">"|"</span> + COUNT_PATTERN + <span class="hljs-string">"|"</span> + EXISTS_PATTERN + <span class="hljs-string">"|"</span> + DELETE_PATTERN + <span class="hljs-string">")((\\p&#123;Lu&#125;.*?))??By"</span>);</span><br><span class="line"></span><br><span class="line">	<span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Subject subject;</span><br><span class="line">	<span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Predicate predicate;</span><br><span class="line"></span><br><span class="line">	<span class="hljs-comment">/**</span></span><br><span class="line"><span class="hljs-comment">	 * Creates a new &#123;<span class="hljs-doctag">@link</span> PartTree&#125; by parsing the given &#123;<span class="hljs-doctag">@link</span> String&#125;.</span></span><br><span class="line"><span class="hljs-comment">	 * </span></span><br><span class="line"><span class="hljs-comment">	 * <span class="hljs-doctag">@param</span> source the &#123;<span class="hljs-doctag">@link</span> String&#125; to parse</span></span><br><span class="line"><span class="hljs-comment">	 * <span class="hljs-doctag">@param</span> domainClass the domain class to check individual parts against to ensure they refer to a property of the</span></span><br><span class="line"><span class="hljs-comment">	 *          class</span></span><br><span class="line"><span class="hljs-comment">	 */</span></span><br><span class="line">	<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">PartTree</span><span class="hljs-params">(String source, Class&lt;?&gt; domainClass)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">		Assert.notNull(source, <span class="hljs-string">"Source must not be null"</span>);</span><br><span class="line">		Assert.notNull(domainClass, <span class="hljs-string">"Domain class must not be null"</span>);</span><br><span class="line"></span><br><span class="line">		Matcher matcher = PREFIX_TEMPLATE.matcher(source);</span><br><span class="line">		<span class="hljs-keyword">if</span> (!matcher.find()) &#123;</span><br><span class="line">			<span class="hljs-keyword">this</span>.subject = <span class="hljs-keyword">new</span> Subject(<span class="hljs-keyword">null</span>);</span><br><span class="line">			<span class="hljs-keyword">this</span>.predicate = <span class="hljs-keyword">new</span> Predicate(source, domainClass);</span><br><span class="line">		&#125; <span class="hljs-keyword">else</span> &#123;</span><br><span class="line">			<span class="hljs-keyword">this</span>.subject = <span class="hljs-keyword">new</span> Subject(matcher.group(<span class="hljs-number">0</span>));</span><br><span class="line">			<span class="hljs-keyword">this</span>.predicate = <span class="hljs-keyword">new</span> Predicate(source.substring(matcher.group().length()), domainClass);</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">    <span class="hljs-comment">//...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>配合着注释, 看到除了一些关键字定义外, 还定义了<code>Subject</code>和<code>Predicate</code></p><ul><li><p><strong><code>PartTree.Subject</code></strong>: 主语对象</p><p>for example <code>&quot;findDistinctUserByNameOrderByAge&quot;</code> would have the subject <code>&quot;DistinctUser&quot;</code>.</p></li><li><p><strong><code>PartTree.Predicate</code></strong>: 谓语对象</p><p>for example<code>&quot;findDistinctUserByNameOrderByAge&quot;</code> would have the predicate <code>&quot;NameOrderByAge&quot;</code>.</p></li></ul><h5 id="Subject主语部分"><a href="#Subject主语部分" class="headerlink" title="Subject主语部分"></a>Subject主语部分</h5><figure class="highlight java hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Subject</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">	<span class="hljs-comment">// ...</span></span><br><span class="line">    </span><br><span class="line">    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Subject</span><span class="hljs-params">(String subject)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="hljs-keyword">this</span>.distinct = subject == <span class="hljs-keyword">null</span> ? <span class="hljs-keyword">false</span> : subject.contains(DISTINCT);</span><br><span class="line">        <span class="hljs-keyword">this</span>.count = matches(subject, COUNT_BY_TEMPLATE);</span><br><span class="line">        <span class="hljs-keyword">this</span>.exists = matches(subject, EXISTS_BY_TEMPLATE);</span><br><span class="line">        <span class="hljs-keyword">this</span>.delete = matches(subject, DELETE_BY_TEMPLATE);</span><br><span class="line">        <span class="hljs-keyword">this</span>.maxResults = returnMaxResultsIfFirstKSubjectOrNull(subject);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">	<span class="hljs-comment">// ...		</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Subject(主语对象)的职责很简单: 通过正则提取几个构建<code>PartTree</code>的属性</p><p>也就是几个布尔常量: <code>distince</code>, <code>count</code>, <code>exists</code>, <code>delete</code>, <code>maxResults</code></p><h5 id="Predicate谓语部分"><a href="#Predicate谓语部分" class="headerlink" title="Predicate谓语部分"></a>Predicate谓语部分</h5><figure class="highlight java hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Predicate</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">	<span class="hljs-comment">//...</span></span><br><span class="line"></span><br><span class="line">    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Predicate</span><span class="hljs-params">(String predicate, Class&lt;?&gt; domainClass)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">        String[] parts = split(detectAndSetAllIgnoreCase(predicate), ORDER_BY);</span><br><span class="line"></span><br><span class="line">        <span class="hljs-keyword">if</span> (parts.length &gt; <span class="hljs-number">2</span>) &#123;</span><br><span class="line">            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> IllegalArgumentException(<span class="hljs-string">"OrderBy must not be used more than once in a method name!"</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        buildTree(parts[<span class="hljs-number">0</span>], domainClass);</span><br><span class="line">        <span class="hljs-keyword">this</span>.orderBySource = parts.length == <span class="hljs-number">2</span> ? <span class="hljs-keyword">new</span> OrderBySource(parts[<span class="hljs-number">1</span>], domainClass) : <span class="hljs-keyword">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>构造参数<code>predicate</code>是方法名的谓语部分, <code>domainClass</code>就是domainClass</p><p>可以看到大致做了这几件事:</p><ul><li><code>detectAndSetAllIgnoreCase()</code>删除<code>AllIgnoreCase</code>关键字, 然后设置<code>alwaysIgnoreCase=true</code></li><li>将剩余部分根据<code>orderBy</code>关键字分割成parts数组</li><li>用排序关键字<code>orderBy</code>之前的部分 <code>buildTree()</code></li><li>用排序关键字<code>orderBy</code>之后的部分构建排序部分</li></ul><p>举个例子: 方法名为<code>findDistinctUserByNameOrderByAgeAllIgnoringCase()</code></p><p>解析出的谓语部分为: <code>NameOrderByAgeAllIgnoringCase</code>, 首先会删除<code>AllIgnoreCase</code> 得到<code>NameOrderByAge</code></p><p>再根据排序关键字分割为<code>Name</code>和<code>Age</code>两部分, <code>buildTree()</code>, 设置排序<code>orderBySource</code>为<code>Order By age: ASC</code></p><p><strong>buildTree()</strong></p><p>该方法实际是对谓语部分进行<code>or</code>和<code>and</code>分割, 生成语法树节点的过程.</p><p>Nodes: 首先用<code>or</code>分割, 构建<code>OrPart</code>子节点</p><p>Children: 子节点包含的<code>child node</code>由<code>and</code>继续分割<code>Part</code></p><p>在Part中还设置了每个属性的<code>Type</code>和<code>PropertyPath</code></p><ul><li><strong>Part.Type</strong>: 属性约束的关键字枚举, 例如<code>BETWEEN</code>, <code>IS_NOT_NULL</code>等</li><li><strong>PropertyPath</strong>: 映射对应的<code>domain class</code>中的属性路径, 例如<code>User.name</code>, <code>User.age</code></li></ul><p>到这里整个方法命名查询的PartTreeJpaQuery实例就推导完成了</p><h4 id="命名方法执行过程"><a href="#命名方法执行过程" class="headerlink" title="命名方法执行过程"></a>命名方法执行过程</h4><p>PartTreeJpaQuery实例分析完成后会放入上面讲到的<code>QueryExecutorMethodInterceptor.queries</code>中</p><p>以<code>ConcurrentHashMap&lt;Method, RepositoryQuery&gt; queries</code>的形式存放</p><blockquote><p>注意: Spring容器启动时候, 初始化时就会创建这些实例. 如果定义了错误的命名查询, 在启动时就会抛出异常.</p></blockquote><p>有了<code>queries</code> 在什么时候执行呢?</p><p>还记得<code>RepositoryQuery</code>接口中的方法<code>execute（</code></p><p>通过find used找到上面提到的<code>RepositoryFactorySupport.QueryExecutorMethodInterceptor#doInvoke</code>中.</p><figure class="highlight java hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-function"><span class="hljs-keyword">private</span> Object <span class="hljs-title">doInvoke</span><span class="hljs-params">(MethodInvocation invocation)</span> <span class="hljs-keyword">throws</span> Throwable </span>&#123;</span><br><span class="line"></span><br><span class="line">    Method method = invocation.getMethod();</span><br><span class="line">    Object[] arguments = invocation.getArguments();</span><br><span class="line"></span><br><span class="line">    <span class="hljs-keyword">if</span> (isCustomMethodInvocation(invocation)) &#123;</span><br><span class="line"></span><br><span class="line">        Method actualMethod = repositoryInformation.getTargetClassMethod(method);</span><br><span class="line">        <span class="hljs-keyword">return</span> executeMethodOn(customImplementation, actualMethod, arguments);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="hljs-keyword">if</span> (hasQueryFor(method)) &#123;</span><br><span class="line">        <span class="hljs-comment">// 看这里!</span></span><br><span class="line">        <span class="hljs-keyword">return</span> queries.get(method).execute(arguments);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="hljs-comment">// Lookup actual method as it might be redeclared in the interface</span></span><br><span class="line">    <span class="hljs-comment">// and we have to use the repository instance nevertheless</span></span><br><span class="line">    Method actualMethod = repositoryInformation.getTargetClassMethod(method);</span><br><span class="line">    <span class="hljs-keyword">return</span> executeMethodOn(target, actualMethod, arguments);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>看到了吧? 在实际调用时, 会直接从<code>queries</code>缓存中获取<code>RepositoryQuery</code>, 执行查询.</p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>整个推导过程到最后, 实际就是去看<code>PartTree</code>语法生成树的逻辑.</p><p>无论是SQL解析器的逆向解析SQL语义还是本节<code>PartTree</code>中根据语义解析出SQL.</p><p>对我们来说这都不是重点. 所以本节大量篇幅都放在根据方法命名推导的原理上</p><p>包括Repository的工厂类 -&gt; JpaRepositoryFactoryBean的由来和Bean初始化后的一系列回调操作</p><p>并补充了Repository及其子接口和lookupStrategy查询策略的相关知识.</p><p>所以涉及到了<code>Spring Data Common</code>和<code>Spring Data JPA</code>模块的接口和实现类.</p><p>所以最懵的可能就是这些接口和实现的关系了.</p><p>所以这里引用一张UML图: <a href="http://www.dewafer.com/uploads/2017/2017-02-21-JpaRepoUML.png" target="_blank" rel="noopener">http://www.dewafer.com/uploads/2017/2017-02-21-JpaRepoUML.png</a></p></div><ul class="post-copyright"><li><strong>本文标题：</strong><a href="https://xiefayang.com/2018/06/01/Spring Data JPA 源码分析 - 方法命名查询/">Spring Data JPA源码分析-方法命名查询</a></li><li><strong>本文作者：</strong><a href="https://xiefayang.com">i蝸居年華_谢谢谢</a></li><li><strong>本文链接：</strong><a href="https://xiefayang.com/2018/06/01/Spring Data JPA 源码分析 - 方法命名查询/">https://xiefayang.com/2018/06/01/Spring Data JPA 源码分析 - 方法命名查询/</a></li><li><strong>发布时间：</strong>2018-06-01</li><li><strong>版权声明：</strong>本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh" rel="external nofollow" target="_blank">CC BY-NC-SA 4.0</a> 许可协议。转载请注明出处！</li></ul><div class="level is-size-7 is-uppercase"><div class="level-start"><div class="level-item"> <span class="is-size-6 has-text-grey has-mr-7">#</span> <a class="has-link-grey -link" href="/tags/Spring-Data/">Spring Data</a>, <a class="has-link-grey -link" href="/tags/Spring全家桶/">Spring全家桶</a>, <a class="has-link-grey -link" href="/tags/源码分析/">源码分析</a></div></div></div><div class="social-share"></div><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/social-share.js@1.0.16/dist/css/share.min.css"><script src="https://cdn.jsdelivr.net/npm/social-share.js@1.0.16/dist/js/social-share.min.js"></script></div></div><div class="card"><div class="card-content"><h3 class="menu-label has-text-centered">坚持原创技术分享，您的支持将鼓励我继续创作！</h3><div class="buttons is-centered"><a class="button is-info donate"><span class="icon is-small"><i class="fab fa-alipay"></i></span> <span>支付宝</span><div class="qrcode"><img src="/images/no2.jpg" alt="支付宝"></div></a><a class="button is-success donate"><span class="icon is-small"><i class="fab fa-weixin"></i></span> <span>微信</span><div class="qrcode"><img src="/images/no.gif" alt="微信"></div></a></div></div></div><div class="card card-transparent"><div class="level post-navigation is-flex-wrap is-mobile"><div class="level-start"><a class="level level-item has-link-grey article-nav-prev" href="/2018/07/16/年中总结与目标OKR/"><i class="level-item fas fa-chevron-left"></i> <span class="level-item">年中总结与目标OKR</span></a></div><div class="level-end"> <a class="level level-item has-link-grey article-nav-next" href="/2018/01/01/Elasticsearch初探/"><span class="level-item">Elasticsearch初探</span><i class="level-item fas fa-chevron-right"></i></a></div></div></div><div class="card"><div class="card-content"><h3 class="title is-5 has-text-weight-normal">评论</h3><div id="valine-thread" class="content"></div><script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script><script src="/js/Valine.min.js"></script><script>new Valine({el:"#valine-thread",notify:!1,verify:!1,app_id:"yVPV2fAKC1B90Xnh2JjuPEAb-gzGzoHsz",app_key:"1i9DgF7j75Uq6OuM23etjjE9",placeholder:"Just go go, 支持Markdown~"})</script></div></div></div><div class="column is-4-tablet is-4-desktop is-3-widescreen has-order-1 column-left"><div class="card widget"><div class="card-content"><nav class="level"><div class="level-item has-text-centered"><div> <img class="image is-128x128 has-mb-6" src="/images/avatar.png" alt="i蝸居年華_谢谢谢" style="border-radius:50%"><p class="is-size-4 is-block"> i蝸居年華_谢谢谢</p><p class="is-size-6 is-block"> Just for fun</p><p class="is-size-6 is-flex is-flex-center has-text-grey"><i class="fas fa-map-marker-alt has-mr-7"></i> <span>Beijing, China</span></p></div></div></nav><nav class="level is-mobile"><div class="level-item has-text-centered is-marginless"><div><p class="heading"> 文章</p><p class="title has-text-weight-normal"> 49</p></div></div><div class="level-item has-text-centered is-marginless"><div><p class="heading"> 分类</p><p class="title has-text-weight-normal"> 13</p></div></div><div class="level-item has-text-centered is-marginless"><div><p class="heading"> 标签</p><p class="title has-text-weight-normal"> 32</p></div></div></nav><div class="level"> <a class="level-item button is-link is-rounded" href="https://github.com/thank037" target="_blank">关注我</a></div><div class="level is-mobile"> <a class="level-item button is-white is-marginless" target="_blank" title="github" href="https://github.com/thank037" name><img src="/images/github.svg" style="width:20px;height:20px"></a> <a class="level-item button is-white is-marginless" target="_blank" title="gitee" href="https://gitee.com/thank037" name><img src="/images/gitee.svg" style="width:20px;height:20px"></a> <a class="level-item button is-white is-marginless" target="_blank" title="weibo" href="https://weibo.com/u/2139090054" name><img src="/images/weibo.svg" style="width:20px;height:20px"></a> <a class="level-item button is-white is-marginless" target="_blank" title="email" href="mailto:coderthank@163.com" name><img src="/images/email.svg" style="width:20px;height:20px"></a></div></div></div><div class="card widget column-left is-sticky" id="toc"><div class="card-content"><div class="menu"><h3 class="menu-label"> 文章目录</h3><ul class="menu-list"><li> <a class="is-flex" href="#前言"><span class="has-mr-6">1</span> <span>前言</span></a></li><li> <a class="is-flex" href="#方法命名查询"><span class="has-mr-6">2</span> <span>方法命名查询</span></a><ul class="menu-list"><li> <a class="is-flex" href="#Repository作用"><span class="has-mr-6">2.1</span> <span>Repository作用</span></a></li><li> <a class="is-flex" href="#Spring-Data-公共模块"><span class="has-mr-6">2.2</span> <span>Spring Data 公共模块</span></a></li></ul></li><li> <a class="is-flex" href="#源码分析"><span class="has-mr-6">3</span> <span>源码分析</span></a><ul class="menu-list"><li> <a class="is-flex" href="#Repository工厂类"><span class="has-mr-6">3.1</span> <span>Repository工厂类</span></a></li><li> <a class="is-flex" href="#RepositoryFactorySupport-factory的创建"><span class="has-mr-6">3.2</span> <span>RepositoryFactorySupport#factory的创建</span></a></li><li> <a class="is-flex" href="#lookupStrategy方法查询定义的查找策略"><span class="has-mr-6">3.3</span> <span>lookupStrategy方法查询定义的查找策略</span></a></li><li> <a class="is-flex" href="#PartTree语法树"><span class="has-mr-6">3.4</span> <span>PartTree语法树</span></a><ul class="menu-list"><li> <a class="is-flex" href="#Subject主语部分"><span class="has-mr-6">3.4.1</span> <span>Subject主语部分</span></a></li><li> <a class="is-flex" href="#Predicate谓语部分"><span class="has-mr-6">3.4.2</span> <span>Predicate谓语部分</span></a></li></ul></li><li> <a class="is-flex" href="#命名方法执行过程"><span class="has-mr-6">3.5</span> <span>命名方法执行过程</span></a></li></ul></li><li> <a class="is-flex" href="#总结"><span class="has-mr-6">4</span> <span>总结</span></a></li></ul></div></div></div><div class="column-right-shadow is-hidden-widescreen is-sticky"></div></div></div></div></section><footer class="footer"><div class="container"><div class="level"><div class="level-start has-text-centered-mobile"> <a class="footer-logo is-block has-mb-6" href="/">i蝸居年華_谢谢谢</a><p class="is-size-7"> &copy; 2016—2021 i蝸居年華_谢谢谢&nbsp; 由<a href="http://hexo.io/" target="_blank">Hexo</a> 强力驱动<br> <span id="busuanzi_container_site_uv">您是第<span id="busuanzi_value_site_uv">0</span>个访客</span></p></div><div class="level-end"><div class="field has-addons is-flex-center-mobile has-mt-5-mobile is-flex-wrap is-flex-middle"><p class="control"><a class="button is-white is-large" target="_blank" title="Creative Commons" href="https://creativecommons.org/"><i class="fab fa-creative-commons"></i></a></p><p class="control"><a class="button is-white is-large" target="_blank" title="Attribution 4.0 International" href="https://creativecommons.org/licenses/by/4.0/"><i class="fab fa-creative-commons-by"></i></a></p><p class="control"><a class="button is-white is-large" target="_blank" title="Download on GitHub" href="https://github.com/thank037"><i class="fab fa-github"></i></a></p></div></div></div></div></footer><script src="https://cdn.jsdelivr.net/npm/jquery@3.3.1/dist/jquery.min.js"></script><script src="https://cdn.jsdelivr.net/npm/moment@2.22.2/min/moment-with-locales.min.js"></script><script>moment.locale("zh-CN")</script><script src="/js/animation.js"></script><script src="https://cdn.jsdelivr.net/npm/lightgallery@1.6.8/dist/js/lightgallery.min.js" defer="defer"></script><script src="https://cdn.jsdelivr.net/npm/justifiedGallery@3.7.0/dist/js/jquery.justifiedGallery.min.js" defer="defer"></script><script src="/js/gallery.js" defer="defer"></script><div id="outdated"><h6>Your browser is out-of-date!</h6><p>Update your browser to view this website correctly. <a id="btnUpdateBrowser" href="http://outdatedbrowser.com/">Update my browser now</a></p><p class="last"><a href="#" id="btnCloseUpdateBrowser" title="Close">&times;</a></p></div><script src="https://cdn.jsdelivr.net/npm/outdatedbrowser@1.1.5/outdatedbrowser/outdatedbrowser.min.js" defer="defer"></script><script>document.addEventListener("DOMContentLoaded",function(){outdatedBrowser({bgColor:"#f25648",color:"#ffffff",lowerThan:"flex"})})</script><script src="https://cdn.jsdelivr.net/npm/mathjax@2.7.5/unpacked/MathJax.js?config=TeX-MML-AM_CHTML" defer="defer"></script><script>document.addEventListener("DOMContentLoaded",function(){MathJax.Hub.Config({"HTML-CSS":{matchFontHeight:!1},SVG:{matchFontHeight:!1},CommonHTML:{matchFontHeight:!1},tex2jax:{inlineMath:[["$","$"],["\\(","\\)"]]}})})</script><a id="back-to-top" title="回到顶端" href="javascript:;"><i class="fas fa-chevron-up"></i></a><script src="/js/back-to-top.js" defer="defer"></script><script src="https://cdn.jsdelivr.net/npm/clipboard@2.0.4/dist/clipboard.min.js" defer="defer"></script><script src="/js/clipboard.js" defer="defer"></script><script src="/js/main.js" defer="defer"></script><div class="searchbox ins-search"><div class="searchbox-container ins-search-container"><div class="searchbox-input-wrapper"> <input type="text" class="searchbox-input ins-search-input" placeholder="全文搜索站内文章~"><span class="searchbox-close ins-close ins-selectable"><i class="fa fa-times-circle"></i></span></div><div class="searchbox-result-wrapper ins-section-wrapper"><div class="ins-section-container"></div></div></div></div><script>window.INSIGHT_CONFIG={TRANSLATION:{POSTS:"文章",PAGES:"页面",CATEGORIES:"分类",TAGS:"标签",UNTITLED:"(无标题)"},CONTENT_URL:"/content.json"}</script><script src="/js/insight.js" defer="defer"></script><link rel="stylesheet" href="/css/search.css"><link rel="stylesheet" href="/css/insight.css"><script src="/live2dw/lib/L2Dwidget.min.js?094cbace49a39548bed64abff5988b05"></script><script>L2Dwidget.init({pluginRootPath:"live2dw/",pluginJsPath:"lib/",pluginModelPath:"assets/",tagMode:!1,debug:!1,model:{jsonPath:"/live2dw/assets/hijiki.model.json"},display:{position:"right",width:150,height:300,mobile:{show:!0},react:{opacity:.7}},log:!1})</script></body></html>